<!-- Copyright 2013 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -->
<!DOCTYPE html>
<title>Unit Test of e2e.openpgp.Context</title>
<script src="../../../closure/base.js"></script>
<script src="test_js_deps-runfiles.js"></script>
<script>
  goog.require('goog.testing.AsyncTestCase');
  goog.require('goog.testing.MockControl');
  goog.require('goog.testing.jsunit');
  goog.require('e2e.openpgp.Context');
  goog.require('e2e.openpgp.ContextImpl');
</script>
<script>
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(document.title);
asyncTestCase.stepTimeout = 10000;

var publicKeyAsciiOther =  // ECDSA key, ECDH subkey.
  '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
  'Version: GnuPG v2.1.0-ecc (GNU/Linux)\n' +
  '\n' +
  'mFIEUh5AoBMIKoZIzj0DAQcCAwT68182duSEJFXKa+qkBa0Vgeswnv8GP8tKYiU/\n' +
  'MCZd6dGTvrtf2gSjyAsVkB0V0idW7i8yW1wfh3y2AbGWDr/dtB9lY2MgcmVhbCBu\n' +
  'YW1lIDxlY2NAZXhhbXBsZS5jb20+iHoEExMIACIFAlIeQKACGwMGCwkIBwMCBhUI\n' +
  'AgkKCwQWAgMBAh4BAheAAAoJEOrrinav6MhnxtcA/iAteDFo/P5SU5XV/8/4BN9x\n' +
  'f28SuvwFipnjjyOmvB0eAP4kPM5LAp2EW+QIyG6+CJP1No9uWyZTdLPkTRgLtYhi\n' +
  'GLhWBFIeQKASCCqGSM49AwEHAgMEgk1dVpgPCM38NBNoBcvehm7mt6aUmK8mDb/M\n' +
  'SHo2/NlwfTh+BDCoVX5asSetzuW2RbnP6sCBwfsuLSrSWUVauwMBCAeIYQQYEwgA\n' +
  'CQUCUh5AoAIbDAAKCRDq64p2r+jIZzqQAQCcv0VOQFiNOM6JNdLHTqlCYxeoz09d\n' +
  'UP3LdgcnLED/YwD9FqcNrkok9BuXJ9+rXTSu+uqdWB7gpMO9mfk65d5IQ+s=\n' +
  '=xRCj\n' +
  '-----END PGP PUBLIC KEY BLOCK-----';
var publicKeyAscii =
  '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
  'Version: GnuPG v1.4.11 (GNU/Linux)\n' +
  '\n' +
  'mQENBFKFJ5kBCADgTy3vZ27fhfiFDUSsCx5ldIHwE1GbuZCm+WIhCH3ni2CL0lKY\n' +
  'AUNFWRcxeODlQlvYA2ti+JALWjzlUYTpf02g3LfzBSIqdDn3JBwN6U12A0xwwSRs\n' +
  'KSRp8phn9yCyCTO7btbqZtGc29jrX/k6cbrBE8ln/9wlZVx8KVcpzDI4jhXjUwmM\n' +
  'JYvJqCHXRTe3kEGZkONfAeeIBfvs7t7Y/sv80RuXnlwMVMjEZKGRFlNQ5zqr8QQb\n' +
  'qh9MA7NeezIi890cQzii/+w6ta7X4/BnML4iud/aNiLYhpeZwEFqXHMwp+w8xHkk\n' +
  'YharW9Kgng6Vw7UE10SxeUqCjaLnlSf82Z5TABEBAAG0RVJTQVJTQSBLRVkgKFJT\n' +
  'QS9SU0Ega2V5IGdlbmVyYXRlZCBieSBncGcpIDxyc2Fyc2FAcGdwa2V5LmV4YW1w\n' +
  'bGUuY29tPokBOAQTAQIAIgUCUoUnmQIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgEC\n' +
  'F4AACgkQj2UmmMMwpyqPWgf+Jc4DzCCxf3S1M7zM49y7VNne9H2Fy2TCyvidhlXd\n' +
  'OpI4dNKjIyUmYQZTLEWcWpnxcK5YcypxDrFX69s0F5s8cd9CN2gIRglHSHpay8oj\n' +
  '2NTJ8DS05pmUyeKlG429zah0n1OtHsCOq/E/ibDmDLUsN2/ECh6ZNxpVIig5+sin\n' +
  'JpP5n2j1vO3QbGs2pggaY5CWjayN2MyjIaIWyPrq/H08LPJPV41rFdU9Dur7/Eby\n' +
  'cclQ6yM2fg8D+wzK2gH4zvxSIpegD7vA5+GN2RYP3iYVx7i+DK8PQdTBe/qL2WDa\n' +
  'Kpy7DyhL5P4WNwPo51GHP5TBNvL1z0IzqyfcVuZtRVZ64LkBDQRShSeZAQgAuR/p\n' +
  'QU1Laihb2ladg6ymg+Crs5ap3cdjeW+vPQ8E4uVi3oZXfpfpHLYTY6EchVMJKU9g\n' +
  'oZFn/z2vb67h5/vSARIXFTU5ccwzegW6lG+zPcY+QxpmkjMiLlpDN7cMu9oJE44+\n' +
  'I3KFhUQkkEPGB5BznnsCCr10Cwe1Tf6LDs/BvrXBPoi4/ex329yFU5hbp5OWzylJ\n' +
  'Ak4hcPVMiYe0YFNb4VHUGwfwWqlgHuHfKWfbN6E7F0EaZosGZALGt981iNA+P1uQ\n' +
  '1rzm0dMaqKwRh2IAPeCDeiIBkbFgoae8eE/4f32xpV+8J0nUYZCKZjhxZYbAJfDx\n' +
  '1SY0HZ6ex3MKscVgBwARAQABiQEfBBgBAgAJBQJShSeZAhsMAAoJEI9lJpjDMKcq\n' +
  'MfcH/As1jLNy+H62LG/kgXL4i4mhjouI4Og54FKZsvcmJHM/2JX3TsYhU0PAJ4MB\n' +
  'fHyNZ9BUiuFpsQupERQ9nOxkMUL5nm4xn5dBVa4ZumUcnr/kIib5gFhs6nHt5LUo\n' +
  '1XeiCW7bt0ac7hOTECox+ZM5YabBO+cXLfdCUQp7vDh4xfxjnX7+fviLvY32nboX\n' +
  'rxA+hcluYFJdl87kTatUir6vA7I7KBtlwnj2dRaM1BiFmXmbj6eHnBZ5YxCh1yfq\n' +
  'mcR6P4l2NfaqJ5k4olWcWTLmjlwNMsWRL26zFRcpbpmle1F5rCx0CS0XXAHLJmIe\n' +
  '6LOU9E37DV4E77Ali8P/7IbNyqE=\n' +
  '=A9tz\n' +
  '-----END PGP PUBLIC KEY BLOCK-----';

var privateKeyAscii =
  '-----BEGIN PGP PRIVATE KEY BLOCK-----\n' +
  'Version: GnuPG v1.4.11 (GNU/Linux)\n' +
  '\n' +
  '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\n' +
  '/+yGzcqh\n' +
  '=no9X\n' +
  '-----END PGP PRIVATE KEY BLOCK-----';
var USER_ID = 'RSARSA KEY (RSA/RSA key generated by gpg) <rsarsa@pgpkey.example.com>';
var KEY_PW = 'test';
var passwordCallback = function(m, c) {c(KEY_PW);};
var context = null;
var mockControl_;

function setUp() {
  localStorage.clear();
  context = new e2e.openpgp.ContextImpl(true);  // armorOutput
  context.setKeyRingPassphrase(KEY_PW);
  mockControl_ = new goog.testing.MockControl();
}


function tearDown() {
  mockControl_.$tearDown();
}


function testMojibakeInternally() {
  var mojibake = '文字化け';
  var krakozyabry = 'кракозя́бры';
  var floodResistantMirrorDrillingMachine = 'Árvíztűrő tükörfúrógép';
  var luanman = '乱码';
  context.importKey(passwordCallback, privateKeyAscii);
  context.importKey(passwordCallback, publicKeyAscii);
  context.setArmorHeader('Comment', 'маймуница');
  asyncTestCase.waitForAsync('waiting for encryptSign1.');
  context.encryptSign(
    mojibake, // plaintext
    {}, // options
    [], // encrypt keys
    [floodResistantMirrorDrillingMachine, krakozyabry] // passphrases
  ).addCallback(function(enc) {
      asyncTestCase.waitForAsync('waiting for veriDecrypt1.');
      assertContains('Comment: маймуница', enc);
      return context.verifyDecrypt(
        function(uid, cb) {
          cb(floodResistantMirrorDrillingMachine);
        }, enc).addCallback(function(res) {
          assertEquals(mojibake, e2e.byteArrayToString(res.decrypt.data));
          return enc;
        });
    }).addCallback(function(enc) {
      asyncTestCase.waitForAsync('waiting for veriDecrypt2.');
      return context.verifyDecrypt(function(uid, cb) {
        cb(krakozyabry);
      }, enc);
    }).addCallback(function(res) {
      asyncTestCase.waitForAsync('waiting for searchPrivateKey1.');
      assertEquals(mojibake, e2e.byteArrayToString(res.decrypt.data));
      return context.searchPrivateKey(USER_ID);
    }).addCallback(function(res) {
      asyncTestCase.waitForAsync('waiting for encryptSign2.');
      assert(res.length > 0);
      return context.encryptSign(
        luanman, {}, [], [], res[0]);
    }).addCallback(function(res) {
      asyncTestCase.waitForAsync('waiting for verifyClearSign1.');
      assertContains(luanman, res);
      return context.verifyClearSign(res);
    }).addCallback(function(res) {
      assert(res);
      // por fín!
      asyncTestCase.continueTesting();
    }).addErrback(function(err) {
      throw err;
    });
}

function testEncrypt() {
  context.importKey(passwordCallback, publicKeyAscii);
  var rsaKeys = e2e.async.Result.getValue(context.searchPublicKey(USER_ID));
  assertEquals(1, rsaKeys.length);

  context.importKey(passwordCallback, publicKeyAsciiOther);
  var allKeys = new goog.structs.Map(e2e.async.Result.getValue(context.getAllKeys()));
  var keys = goog.array.flatten(allKeys.getValues());
  var plaintext = 'hello world from context message';
  context.importKey(passwordCallback, privateKeyAscii);
  asyncTestCase.waitForAsync('waiting for encryption.');
  context.encryptSign(
      plaintext,
      [], // Options.
      keys, // Keys to encrypt to.
      [], // Passphrases for symmetric ESKs.
      null // Keys to sign with.
  ).addCallback(function(encrypted) {
    asyncTestCase.waitForAsync('waiting for decryption.');
    context.verifyDecrypt(passwordCallback, encrypted).addCallback(function(result) {
      asyncTestCase.continueTesting();
      var data = result['decrypt']['data'];
      assertEquals(plaintext, e2e.byteArrayToString(data));
    }).addErrback(function(error) {
      throw error;
    });
  }).addErrback(function(error) {
      throw error;
  });
}


function testEncryptSign() {
  context.importKey(passwordCallback, publicKeyAscii);
  var allKeys = new goog.structs.Map(e2e.async.Result.getValue(context.getAllKeys()));
  var keys = goog.array.flatten(allKeys.getValues());
  var plaintext = 'hello world from signed message';
  context.importKey(passwordCallback, privateKeyAscii);

  // Test signing.
  var privateKeys = e2e.async.Result.getValue(context.searchPrivateKey(USER_ID));
  var privateKey = privateKeys[0]; // Only one key for signing.
  asyncTestCase.waitForAsync('waiting for encryption.');
  context.encryptSign(
      plaintext,
      [], // Options.
      keys, // Keys to encrypt to.
      [], // Passphrases for symmetric ESKs.
      privateKey // Key to sign with.
    ).addCallback(function(signedEncrypted) {
      asyncTestCase.waitForAsync('waiting for decryption.');
      context.verifyDecrypt(passwordCallback, signedEncrypted).addCallback(function(result) {
        asyncTestCase.continueTesting();
        var data = result['decrypt']['data'];
        var verify = result['verify'];
        assertEquals(plaintext, e2e.byteArrayToString(data));
        assertTrue(goog.isDefAndNotNull(verify));
        assertEquals(0, verify.failure.length);
        assertEquals(1, verify.success.length);
        assertArrayEquals(privateKey.key.fingerprint,
          verify.success[0].key.fingerprint);
        assertTrue(goog.array.contains(verify.success[0].uids, USER_ID));
      }).addErrback(function(error) {
        throw error;
      });
    });
}


function testClearSign() {
  context.importKey(passwordCallback, publicKeyAscii);
  var rsaKeys = e2e.async.Result.getValue(context.searchPublicKey(USER_ID));
  assertEquals(1, rsaKeys.length);

  context.importKey(passwordCallback, publicKeyAsciiOther);
  var allKeys = new goog.structs.Map(e2e.async.Result.getValue(context.getAllKeys()));
  var keys = goog.array.flatten(allKeys.getValues());
  var plaintext = 'hello world from signed message';
  context.importKey(passwordCallback, privateKeyAscii);

  // Test signing.
  var privateKeys = e2e.async.Result.getValue(context.searchPrivateKey(USER_ID));
  var privateKey = privateKeys[0]; // Only one key for signing.

  // Text clear signed.
  asyncTestCase.waitForAsync('waiting for clear sign.');
  context.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      [], // Passphrases for symmetric ESKs.
      privateKey  // Key to sign with.
    ).addCallback(function(clearSigned) {
      asyncTestCase.continueTesting();
      asyncTestCase.waitForAsync('waiting for clear sign verification.');
      context.verifyClearSign(clearSigned).addCallback(function(result) {
        asyncTestCase.continueTesting();
        assertTrue(result);
      }).addErrback(function(error) {
        throw error;
      });
    });
}

function testEncryptSymmetric() {
  var plaintext = 'plaintext for symmetric';
  var res = context.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      ['someSecret', KEY_PW, 'anotherSecret']); // Passphrases for symmetric ESKs.
  var encrypted = e2e.async.Result.getValue(res);
  asyncTestCase.waitForAsync('waiting for decryption.');
  context.verifyDecrypt(passwordCallback, encrypted).addCallback(function(result) {
    var data = result['decrypt']['data'];
    assertEquals(plaintext, e2e.byteArrayToString(data));
    asyncTestCase.continueTesting();
  }).addErrback(function(error) {
    throw error;
  });
}

function testEncryptToNoone() {
  var plaintext = 'plaintext';
  asyncTestCase.waitForAsync('waiting for encrypt.');
  context.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      [] // Passphrases for symmetric ESKs.
  ).addCallback(function(res) {
    fail("Encryption to noone should throw error");
  }).addErrback(function(error) {
    assertTrue(error instanceof e2e.openpgp.error.InvalidArgumentsError);
    asyncTestCase.continueTesting();
  });
}


// Test that if a passphrase fails to decrypt any ESKs, we ask for a new passphrase.
function testEncryptSymmetricRetry() {
  var passwordCount = 0;
  var correctPasswordNum = 2;
  var passwordCallbackInc = function(message, callback) {
    passwordCount = passwordCount + 1;
    assertTrue('Correct password was not recognized.', passwordCount <= correctPasswordNum);
    callback(KEY_PW + passwordCount);
  };
  localStorage.clear();
  var contextInc = new e2e.openpgp.ContextImpl(true);  // armorOutput
  contextInc.setKeyRingPassphrase(KEY_PW);

  var plaintext = 'plaintext for symmetric';
  var res = contextInc.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      ['someSecret', KEY_PW + correctPasswordNum, 'anotherSecret']); // Passphrases for symmetric ESKs.
  var encrypted = e2e.async.Result.getValue(res);

  asyncTestCase.waitForAsync('waiting for decryption.');
  contextInc.verifyDecrypt(passwordCallbackInc, encrypted).addCallback(function(result) {
    var data = result['decrypt']['data'];
    assertEquals(plaintext, e2e.byteArrayToString(data));
    asyncTestCase.continueTesting();
  }).addErrback(function(error) {
    throw error;
  });
}

function testVerifyClearSign() {
  context.importKey(passwordCallback, privateKeyAscii);
  context.importKey(passwordCallback, publicKeyAscii);
  var privateKeys = e2e.async.Result.getValue(context.searchPrivateKey(USER_ID));
  var privateKey = privateKeys[0]; // Only one key for signing.
  var plaintext = "cleartext message";
  asyncTestCase.waitForAsync('waiting for clear sign.');
  context.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      [], // Passphrases for symmetric ESKs.
      privateKey  // Key to sign with.
    ).addCallback(function(clearSigned) {
      asyncTestCase.continueTesting();
      asyncTestCase.waitForAsync('waiting for clear sign verification.');
      context.verifyClearSign(clearSigned).addCallback(function(result) {
        asyncTestCase.continueTesting();
        assertTrue(result);
      }).addErrback(function(error) {
        throw error;
      });
      // change the message
      clearSigned = clearSigned.replace('cleartext', 'modified');
      asyncTestCase.waitForAsync('waiting for clear sign verification.');
      context.verifyClearSign(clearSigned).addCallback(function(result) {
        asyncTestCase.continueTesting();
        assertFalse(result);
      }).addErrback(function(error) {
        throw error;
      });
    });
}

function testDecryptNoKey() {
  context.importKey(passwordCallback, publicKeyAscii);
  var keys = e2e.async.Result.getValue(context.searchPublicKey(USER_ID));
  assertEquals(1, keys.length);

  var plaintext = 'hello world from context message';
  var res = context.encryptSign(
      plaintext,
      [], // Options.
      keys, // Keys to encrypt to.
      [], // Passphrases for symmetric ESKs.
      null);  // Keys to sign with.
  var encrypted = e2e.async.Result.getValue(res);

  asyncTestCase.waitForAsync('waiting for decryption to fail.');
  context.verifyDecrypt(passwordCallback, encrypted).addErrback(function(result) {
    asyncTestCase.continueTesting();
  });
}


function testParseErrorsDontAskForPassphrase() {
  var alreadyCalled = false;
  var passwordCallbackOnce = function(message, callback) {
    assertFalse('Correct password was not recognized.', alreadyCalled);
    alreadyCalled = true;
    callback(KEY_PW);
  };
  localStorage.clear();
  var contextInc = new e2e.openpgp.ContextImpl(true);  // armorOutput
  contextInc.setKeyRingPassphrase(KEY_PW);

  var plaintext = 'plaintext for symmetric';
  var res = contextInc.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      [KEY_PW]); // Passphrases for symmetric ESKs.
  var encrypted = e2e.async.Result.getValue(res);

  asyncTestCase.waitForAsync('waiting for decryption.');

  var mock = mockControl_.createMethodMock(e2e.openpgp.block.LiteralMessage.prototype, 'getData');
  mock().$throws(new e2e.openpgp.error.ParseError('Simulating invalid literal package.'));

  mockControl_.$replayAll();

  contextInc.verifyDecrypt(passwordCallbackOnce, encrypted).addCallback(function(result) {
    fail('e2e.openpgp.error.ParseError should be thrown');
  }).addErrback(function(error) {
    assertTrue(error instanceof e2e.openpgp.error.ParseError);
    asyncTestCase.continueTesting();
    mockControl_.$verifyAll();
  });
}


function testNestingLimitOnDecryption() {
  localStorage.clear();
  var contextInc = new e2e.openpgp.ContextImpl(true);  // armorOutput
  contextInc.setKeyRingPassphrase(KEY_PW);

  var plaintext = 'plaintext for symmetric';
  var res = contextInc.encryptSign(
      plaintext,
      [], // Options.
      [], // Keys to encrypt to.
      [KEY_PW]); // Passphrases for symmetric ESKs.
  var encrypted = e2e.async.Result.getValue(res);

  asyncTestCase.waitForAsync('waiting for decryption.');

  // simulate a compressed block quine - see CVE-2013-4402
  var mock = mockControl_.createMethodMock(e2e.openpgp.block.Compressed.prototype,
                                          'getBlock');
  mock().$times(contextInc.MAX_COMPRESSION_NESTING_LEVEL)
        .$returns(new e2e.openpgp.block.Compressed());
  mockControl_.$replayAll();
  contextInc.verifyDecrypt(passwordCallback, encrypted).addCallback(function(result) {
    fail('e2e.openpgp.error.ParseError should be thrown');
  }).addErrback(function(error) {
    assertTrue(error instanceof e2e.openpgp.error.ParseError);
    asyncTestCase.continueTesting();
    mockControl_.$verifyAll();
  });
}


function testGenerateKey() {
  context.generateKey('ECDSA', 256, 'ECDH', 256, 'name', '', 'name@example.com', 1);
  var keys = new goog.structs.Map(e2e.async.Result.getValue(context.getAllKeys()));
  keys = goog.array.flatten(keys.getValues());
  assertEquals(2, keys.length);
  goog.array.forEach(keys, function(key) {
    goog.array.forEach(key.uids, function(uid) {
      assertEquals('name <name@example.com>', uid);
    });
  });
  assertEquals('ECDSA', keys[0].key.algorithm);
  assertEquals('ECDH', keys[1].subKeys[0].algorithm);
}

function testGenerateEmailOnlyKey() {
  context.generateKey('ECDSA', 256, 'ECDH', 256, '', '', 'name@example.com', 1);
  var keys = new goog.structs.Map(e2e.async.Result.getValue(context.getAllKeys()));
  keys = goog.array.flatten(keys.getValues());
  assertEquals(2, keys.length);
  goog.array.forEach(keys, function(key) {
    goog.array.forEach(key.uids, function(uid) {
      assertEquals('<name@example.com>', uid);
    });
  });
  assertEquals('ECDSA', keys[0].key.algorithm);
  assertEquals('ECDH', keys[1].subKeys[0].algorithm);
}

function testImportKeyCallsPassphraseOnce() {
  var called = false;
  asyncTestCase.waitForAsync('Waiting for import key.');
  context.importKey(function(uid, callback) {
    if (called) fail('Import key called more than once.');
    called = true;
    asyncTestCase.continueTesting();
    callback(KEY_PW);
  }, privateKeyAscii);
}


function testImportKeyMissingPassphrase() {
  var numCallsToPassphraseCb = 0;
  asyncTestCase.waitForAsync('Waiting for import key.');
  context.importKey(function(uid, callback) {
    numCallsToPassphraseCb++;
    if (numCallsToPassphraseCb == 1) {
      asyncTestCase.continueTesting();
      asyncTestCase.waitForAsync('Waiting for second import key.');
      callback();
    } else {
      asyncTestCase.continueTesting();
      callback(KEY_PW);
    }
  }, privateKeyAscii);
}


function testImportKeyIncorrectPassphrase() {
  var numCallsToPassphraseCb = 0;
  asyncTestCase.waitForAsync('Waiting for import key.');
  context.importKey(function(uid, callback) {
    numCallsToPassphraseCb++;
    if (numCallsToPassphraseCb == 1) {
      asyncTestCase.continueTesting();
      asyncTestCase.waitForAsync('Waiting for second import key.');
      callback('foo');
    } else {
      asyncTestCase.continueTesting();
      callback(KEY_PW);
    }
  }, privateKeyAscii);
}


function testImportPublicKeyCallbackCalled() {
  asyncTestCase.waitForAsync('Waiting for import public key.');
  context.importKey(passwordCallback, privateKeyAscii).addCallback(function() {
    asyncTestCase.continueTesting();
  });
}


function testImportSecretKeyCallbackCalled() {
  asyncTestCase.waitForAsync('Waiting for import private key.');
  context.importKey(passwordCallback, publicKeyAscii).addCallback(function() {
    asyncTestCase.continueTesting();
  });
}


function testSetPassphraseImportExportImportSecretKey() {
  // Set passphrase, import a key, export it, and try to import it again.
  localStorage.clear();
  var context = new e2e.openpgp.ContextImpl(true);
  context.setKeyRingPassphrase(KEY_PW);
  asyncTestCase.waitForAsync('Waiting for first import of key.');
  context.importKey(passwordCallback, privateKeyAscii).addCallback(function() {
    asyncTestCase.continueTesting();
    context.exportKeyring(true).addCallback(function(kr) {
      localStorage.clear();
      var context = new e2e.openpgp.ContextImpl(true);
      context.setKeyRingPassphrase(KEY_PW);
      asyncTestCase.waitForAsync('Waiting for second import of key.');
      var counter = 0;
      context.importKey(function(uid, cb) {
        if (counter++ > 100) {
          fail('Failed to decrypt key.');
        }
        cb(KEY_PW);
      }, kr).addCallback(function(res) {
        asyncTestCase.continueTesting();
        assert(res.length > 0);
      })
    });
  });
}


function testSetPassphraseImportExportChangePassphraseImportSecretKey() {
  // Set passphrase, import a key, export it, change passphrase and try to import it again.
  localStorage.clear();
  var context = new e2e.openpgp.ContextImpl(true);
  context.setKeyRingPassphrase(KEY_PW);
  asyncTestCase.waitForAsync('Waiting for first import of key.');
  context.importKey(passwordCallback, privateKeyAscii).addCallback(function() {
    asyncTestCase.continueTesting();
    context.exportKeyring(true).addCallback(function(kr) {
      localStorage.clear();
      var context = new e2e.openpgp.ContextImpl(true);
      context.setKeyRingPassphrase('omg');
      asyncTestCase.waitForAsync('Waiting for second import of key.');
      var counter;
      context.importKey(function(uid, cb) {
        if (counter++ < 100) {
          cb('bbq');
        } else {
          cb('');
        }
      }, kr).addCallback(function(res) {
        asyncTestCase.continueTesting();
        assertArrayEquals([], res);
      });
    });
  });
}


function testGetKeyDescription() {
  var fourKeys =
      '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
      '\n' +
      'xv8AAABSBFir2cUTCCqGSM49AwEHAgMERh6l2ToYyzlvyRSEqkZSAxrXy6TGs6TR\n' +
      'FmAHwW4wtkRtYFoe+DyUbU5qodcyjAFFmVnNxTukBDOQOjPJiOFZ6M3/AAAAH0Ry\n' +
      'ZXcgSGludHogPGFkaGludHpAZ29vZ2xlLmNvbT7G/wAAAFYEWKvaQxIIKoZIzj0D\n' +
      'AQcCAwQAFjV1E/cPxpjJ4WLTKFrr2sEwmLoktmSDClx3SspVAOZkCcSFMXN1lRwP\n' +
      'kkrZzHlaPd41OWMeypKXUX394Y2SAwEIB83/AAAAH0RyZXcgSGludHogPGFkaGlu\n' +
      'dHpAZ29vZ2xlLmNvbT7G/wAAAFIEWBd08xMIKoZIzj0DAQcCAwQoPCb0qYN/0RTv\n' +
      'yXXT+TEcfnUfm7VD30eGRKEL4YqGzTVM0TXkSZ9OOzrZYM1VPL2AW/IPbdVNOIgP\n' +
      'lwMC2RiOzf8AAAAmRWR1YXJkbycgVmVsYSIgPE5hdmE+IDxldm5AZ29vZ2xlLmNv\n' +
      'bT7G/wAAAFYEWBd1ZBIIKoZIzj0DAQcCAwQYeixWdLYjI9Yz4OgJAaCF0LFzrrSe\n' +
      'yqlEpDCdFYlqOXyUBm+hZLpTFqYKQzg6nWrSApT46i/5uM/7ww5MiXULAwEIB83/\n' +
      'AAAAJkVkdWFyZG8nIFZlbGEiIDxOYXZhPiA8ZXZuQGdvb2dsZS5jb20+\n' +
      '=wKcN\n' +
      '-----END PGP PUBLIC KEY BLOCK-----';
  var description = context.getKeyDescription(fourKeys);
  assertContains('811709de7df57e37226db2c872de64e56577db56', description);
  assertContains('49427c18d81a19438eb9fb7790f1d45dcb1b19a7', description);
}
</script>

